/** ***************************************************************************
 * SugarPng.cpp
 * 2012 CoffeeParty Ltd.
 * Date Created: 2012-07-21                                  format YYYY-MM-DD
 * Revision Id: $Id$
 ******************************************************************************
 */

#include "CoffeeCommons.hpp"
#include "CoffeeLogger.hpp"
#include "CoffeeIO.hpp"

#include "SugarImage.hpp"

c_void png_cpexcept_error(png_structp png_ptr, png_const_charp msg)
{
    longjmp(png_jmpbuf(png_ptr), 1);
}

c_void user_read_data_fn(png_structp png_ptr, png_bytep data, png_size_t length)
{
    if (!png_ptr)
        return;

    png_size_t lesize;
    CoffeeFile *qtmpptr = (CoffeeFile*)png_get_io_ptr(png_ptr);
    lesize = (png_size_t)qtmpptr->read((c_bytep)data, (c_uint)length);

    if (lesize != length)
        png_error(png_ptr, "Error reading png");
}

/** ***************************************************************************
 * Note:
 * - Width and Height could hold up to (2^31)
 *
 **** *************************************************************************
 */
SugarPng::SugarPng(c_const_bytep filename)
: filesize(0)
{
   if (!isCompatibleFileFormat(filename, "png"))
    {
        c_print("SugarPng(): File Format Error");
        return;
    }

   if (!open(filename))
    {
        c_print("SugarPng(): Reading Error");
        return;
    }
}

SugarPng::~SugarPng()
{
    if (imgdata)
        rdrop(imgdata);
}

c_size_t SugarPng::getSize() const
{
    return filesize;
}

c_uint SugarPng::getWidth() const
{
    return imgwidth;
}

c_uint SugarPng::getHeight() const
{
    return imgheight;
}

c_int SugarPng::getType()
{
    return imgtype;
}

c_size_t SugarPng::getImageDataSizeInBytes() const
{
    return imgsize;
}

c_size_t SugarPng::getImageDataSizeInPixel() const
{
    return imgwidth * imgheight;
}

c_uint SugarPng::getBitsPerPixel() const
{
    return imgbpp;
}

c_uint SugarPng::getBytesPerPixel() const
{
    return imgbytespp;
}

c_uint SugarPng::getPitch() const
{
    return imgpitch;
}

c_uint SugarPng::getRedMask() const
{
    switch (imgbpp)
    {
    case 8:
        return 0x1F << 11;
    case 16:
        return 0x1F << 10;
    case 24:
    case 32:
        return 0x00FF0000;
    default:
        return 0;
    }
}

c_uint SugarPng::getGreenMask() const
{
    switch (imgbpp)
    {
    case 8:
        return 0x3F << 5;
    case 16:
        return 0x1F << 5;
    case 24:
    case 32:
        return 0x0000FF00;
    default:
        return 0;
    }
}

c_uint SugarPng::getBlueMask() const
{
    switch (imgbpp)
    {
    case 8:
    case 16:
        return 0x1F;
    case 24:
    case 32:
        return 0x000000FF;
    default:
        return 0;
    }
}

c_uint SugarPng::getAlphaMask() const
{
    switch (imgbpp)
    {
    case 8:
        return 0;
    case 16:
        return 0x1 << 15;
    case 24:
        return 0;
    case 32:
        return 0xFF000000;
    default:
        return 0;
    }
}

c_color SugarPng::getPixel(c_uint x, c_uint y) const
{
    if (x >= imgwidth || y >= imgheight)
        return c_color(0);

    switch (imgbpp)
    {
    case 8:
    case 16:
        return c_color(((c_ushort*)imgdata)[y * imgwidth + x]);
    case 24:
        return c_color(((c_uint*)imgdata)[y * imgwidth + x]);
    case 32:
        c_ubyte *p = imgdata + (y * 3) * imgwidth + (x * 3);
        return c_color(255, p[0], p[1], p[2]);
    }

    return c_color(0);
}

c_void SugarPng::setPixel(c_uint x, c_uint y,
    const c_color &color, c_bool blend)
{
    if (x >= imgwidth || y >= imgheight)
        return;

    switch (imgbpp)
    {
    case 8:
    case 16:
        {
            c_ushort *dest = (c_ushort*)(imgdata + (y * imgpitch) + (x << 1));
            *dest = color.color;
        }
        break;
    case 24:
        {
            c_ubyte *dest = imgdata + (y + imgpitch) + (x * 3);
            dest[0] = (c_ubyte)color.red();
            dest[1] = (c_ubyte)color.green();
            dest[2] = (c_ubyte)color.blue();
        }
        break;
    case 32:
        {
            c_uint *dest = (c_uint*)(imgdata + (y + imgpitch) + (x << 2));
            *dest = color.color;
        }
        break;
    }
}


c_bool SugarPng::hasAlpha() const
{
    return imgalpha != 0;
}

c_voidp SugarPng::getData() const
{
    return imgdata;
}

c_voidp SugarPng::ark()
{
    return imgdata;
}

c_void SugarPng::unark() { }

c_void SugarPng::showOGL()
{
    glDisable(GL_DEPTH_TEST); //!< Disable Z coordinates and depth buffer
//    glRasterPos2i((teaContext.getWidth() - mePng->getWidth()) / 2,
//        (teaContext.getHeight() + (mePng->getHeight())) / 2);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glDrawPixels(imgwidth, imgheight, (hasAlpha()) ? GL_RGBA : GL_RGB,
        GL_UNSIGNED_BYTE, imgdata);
    glEnable(GL_DEPTH_TEST);
}

c_bool SugarPng::open(c_const_bytep filename)
{
    CoffeeFile *qptr = new CoffeeFile(filename);
    if (!qptr || !qptr->isOpen())
        return false;

    filesize = qptr->size();
    png_byte buffer[8];
    qptr->read((c_bytep)buffer, 8);
    if (png_sig_cmp(buffer, 0, 8))
    {
        drop(qptr);
        c_print("Not a png file!");
        return false;
    }

    png_structp pngptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!pngptr)
    {
        drop(qptr);
        c_print("Out of Memory");
        return false;
    }


    png_infop infoptr = png_create_info_struct(pngptr);
    if (!infoptr)
    {
        drop(qptr);
        png_destroy_read_struct(&pngptr, NULL, NULL);
        c_print("Out of Memory");
        return false;
    }

    if (setjmp(png_jmpbuf(pngptr)))
    {
        drop(qptr);
        png_destroy_read_struct(&pngptr, &infoptr, NULL);
        c_print("Error during init");
        return false;
    }

    png_set_read_fn(pngptr, (c_voidp)qptr, user_read_data_fn); //!< png_init_io(pngptr, fp);
    png_set_sig_bytes(pngptr, 8);
    png_read_info(pngptr, infoptr);

    imgwidth = png_get_image_width(pngptr, infoptr);
    imgheight = png_get_image_height(pngptr, infoptr);
    imgtype = png_get_color_type(pngptr, infoptr);
    imgbpp = png_get_bit_depth(pngptr, infoptr);
    imgfilter = png_get_filter_type(pngptr, infoptr);
    imgcompress = png_get_compression_type(pngptr, infoptr);
    imginterlace = png_get_interlace_type(pngptr, infoptr);
    numofpass = png_set_interlace_handling(pngptr);

    switch (imgtype)
    {
    case PNG_COLOR_TYPE_RGBA:
        imgalpha = 1;
        break;
    case PNG_COLOR_TYPE_RGB:
        imgalpha = 0;
        break;
    case PNG_COLOR_TYPE_PALETTE:
        png_set_palette_to_rgb(pngptr);
        break;
    case PNG_COLOR_TYPE_GRAY_ALPHA:
    case PNG_COLOR_TYPE_GRAY:
        if (imgbpp < 8)
            png_set_expand_gray_1_2_4_to_8(pngptr);
        break;
    default:
        c_print("Color Type: %d", imgtype);
        break;
    }

    if (imgbpp == 16) //!< Use only 8 bits per channel
        png_set_strip_16(pngptr);

    if (png_get_valid(pngptr, infoptr, PNG_INFO_tRNS))
        png_set_tRNS_to_alpha(pngptr);

    png_read_update_info(pngptr, infoptr);
    c_uint rowbytes = png_get_rowbytes(pngptr, infoptr);
    imgdata = new c_ubyte[rowbytes * imgheight];
    if (!imgdata)
    {
        drop(qptr);
        png_destroy_read_struct(&pngptr, &infoptr, NULL);
        c_print("Error creating data storage");
        return false;
    }

    png_bytepp rowptr = new png_bytep[imgheight];
    if (!rowptr)
    {
        rdrop(imgdata);
        drop(qptr);
        png_destroy_read_struct(&pngptr, &infoptr, NULL);
        c_print("Error creating data storage");
        return false;
    }

    for (c_uint i = 0; i < imgheight; ++i)
        rowptr[imgheight - 1 - i] = imgdata + i * rowbytes;

    png_read_image(pngptr, rowptr);
    png_read_end(pngptr, NULL);

    png_destroy_read_struct(&pngptr, &infoptr, NULL);
    rdrop(rowptr);
    drop(qptr);
    return true;
}

c_bool SugarPng::modify(c_const_bytep filename, png_bytepp data)
{
    CoffeeFile *qptr = new CoffeeFile(filename);
    if (!qptr->isOpen())
        return false;

    filesize = qptr->size();

    png_structp pngptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!pngptr)
    {
        drop(qptr);
        c_print("Out of Memory");
        return false;
    }

    png_infop infoptr = png_create_info_struct(pngptr);
    if (!infoptr)
    {
        drop(qptr);
        png_destroy_read_struct(&pngptr, NULL, NULL);
        c_print("Out of Memory");
        return false;
    }

    if (setjmp(png_jmpbuf(pngptr)))
    {
        drop(qptr);
        png_destroy_read_struct(&pngptr, &infoptr, NULL);
        c_print("Error during init");
        return false;
    }

    png_set_read_fn(pngptr, (c_voidp)qptr, user_read_data_fn); //png_init_io(pngptr, fp);
    if (setjmp(png_jmpbuf(pngptr)))
    {
        drop(qptr);
        png_destroy_read_struct(&pngptr, &infoptr, NULL);
        c_print("Error during writing");
        return false;
    }

    png_set_IHDR(pngptr, infoptr, imgwidth, imgheight,
        imgbpp, imgtype, PNG_INTERLACE_NONE,
        PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

    png_write_info(pngptr, infoptr);

    if (setjmp(png_jmpbuf(pngptr)))
    {
        drop(qptr);
        png_destroy_read_struct(&pngptr, &infoptr, NULL);
        c_print("Error during writing bytes");
        return false;
    }

    png_bytepp rowptr = data;

    png_write_image(pngptr, rowptr);

    if (setjmp(png_jmpbuf(pngptr)))
    {
        drop(qptr);
        png_destroy_read_struct(&pngptr, &infoptr, NULL);
        c_print("Error during end of write");
        return false;
    }

    png_write_end(pngptr, NULL);

    for (c_uint y = 0; y < imgheight; y++)
        rdrop(rowptr[y]);

    rdrop(rowptr);
    drop(qptr);
    return true;
}

c_bool SugarPng::isCompatibleFileFormat(c_const_bytep filename, c_const_bytep ext)
{
    c_string tmp = filename;
    c_int epos = tmp.findLast('.');
    if (epos < 0)
        return false;

    epos++;
    if (tmp.equals_ss_ic(ext, epos))
        return true;

    return false;
}

c_dimension<c_uint> SugarPng::getDimension() const
{
    return c_dimension<c_uint>(imgwidth, imgheight);
}

c_void SugarPng::writeToDisk(c_const_bytep filename)
{
}

SugarPng* OpenPNG(c_const_bytep file)
{
    return new SugarPng(file);
}

c_void DeletePNG(SugarPng *ptr)
{
    if (ptr)
        drop(ptr);
}

